package com.apobates.forum.core.impl.service;

import com.apobates.forum.core.dao.BoardDao;
import com.apobates.forum.core.dao.BoardGroupDao;
import com.apobates.forum.core.dao.BoardModeratorDao;
import com.apobates.forum.core.dao.BoardModeratorRoleHistoryDao;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardGroup;
import com.apobates.forum.core.entity.BoardModerator;
import com.apobates.forum.core.entity.BoardModeratorRoleHistory;
import com.apobates.forum.core.entity.ModeratorLevelEnum;
import com.apobates.forum.core.entity.proxy.BoardModeratorReplica;
import com.apobates.forum.core.impl.event.ModeratorBornEvent;
import com.apobates.forum.core.impl.event.ModeratorRecallEvent;
import com.apobates.forum.core.service.BoardModeratorService;
import com.apobates.forum.member.entity.Member;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.utils.lang.TriFunction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.github.davidmarquis.redisq.producer.MessageProducer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

/**
 *
 * @author xiaofanku
 * @since 20200514
 */
@Service
@CacheConfig
public class BoardModeratorServiceImpl implements BoardModeratorService{
    @Autowired
    private BoardGroupDao boardGroupDao;
    @Autowired
    private BoardDao boardDao;
    @Autowired
    private BoardModeratorDao boardModeratorDao;
    @Autowired
    private BoardModeratorRoleHistoryDao boardModeratorRoleHistoryDao;
    @Autowired @Qualifier("moderatorBornProducer")
    private MessageProducer<ModeratorBornEvent> moderatorBornProducer;
    @Autowired @Qualifier("moderatorRecallProducer")
    private MessageProducer<ModeratorRecallEvent> moderatorRecallProducer;
    private final static Logger logger = LoggerFactory.getLogger(BoardModeratorServiceImpl.class);
    
    @Cacheable(value="boardCache", key = "'moderator_'+#boardId", condition="#boardId > 0")
    @Override
    public List<BoardModerator> getAllUsedByBoardId(long boardId) {
        if (boardId > 0) {
            return boardModeratorDao.findUsedByBoardId(boardId);
        }
        return Collections.emptyList();
    }
    
    @CacheEvict(value="boardCache", key="'moderator_'+#boardId", condition="#boardId > 0")
    @Override
    public Optional<BoardModerator> create(int volumesId, long boardId, Member member, ModeratorLevelEnum level)throws IllegalArgumentException {
        if (0 == boardId) { //若是大版主
            return create(volumesId, member, level);
        }
        if (null == member) {
            throw new IllegalArgumentException("会员不存在");
        }
        BoardModerator moderator = new BoardModerator(volumesId, boardId, level, member.getId(), member.getNickname());
        BoardModeratorRoleHistory memberRoleHistory = new BoardModeratorRoleHistory(-1L, member.getMrole(), MemberRoleEnum.BM, volumesId, boardId, member.getId()); //需要回填版主的ID
        
        return storeReplicaModerator(moderator, member, memberRoleHistory);
    }
    
    @Override
    public Optional<BoardModerator> create(int volumesId, Member member, ModeratorLevelEnum level)throws IllegalArgumentException {
        if (null == member) {
            throw new IllegalArgumentException("会员不存在");
        }
        BoardModerator moderator = new BoardModerator(volumesId, 0, level, member.getId(), member.getNickname(), true);
        BoardModeratorRoleHistory memberRoleHistory = new BoardModeratorRoleHistory(-1L, member.getMrole(), MemberRoleEnum.MASTER, volumesId, member.getId()); //需要回填版主的ID
        
        return storeReplicaModerator(moderator, member, memberRoleHistory);
    }
    //在权限检查中使用频繁
    @Override
    public Stream<BoardModeratorReplica> getAllUsedByBoardId(final int boardGroupId, final long boardId) {
        final TriFunction<BoardGroup, Board, BoardModerator, BoardModeratorReplica> action = (bg, b, bm) ->{
            BoardModeratorReplica bmr = Optional.ofNullable(bm).map(BoardModeratorReplica.copyModerator).orElseGet(BoardModeratorReplica::new);
            if(null != b){
                bmr.setBoard(b);
            }
            if (null !=bg && bg.getId() > 0) {
                bmr.setVolumes(bg);
            }
            return bmr;
        };
        CompletableFuture<List<BoardModerator>> bm = CompletableFuture.supplyAsync(()->boardModeratorDao.findUsedByBoardId(boardId)).completeOnTimeout(Collections.emptyList(), 1, TimeUnit.SECONDS);
        CompletableFuture<Stream<BoardModerator>> gbm = CompletableFuture.supplyAsync(()->boardModeratorDao.findAllUsedByBoardGroup(boardGroupId)).completeOnTimeout(Stream.empty(), 1, TimeUnit.SECONDS);
        return bm.thenCombine(gbm, (mb, gmb)->
                Stream.concat(mb.stream(), gmb)).thenCompose(allMS->
                        CompletableFuture.supplyAsync(()->
                                boardDao.findOne(boardId).orElse(Board.empty(boardId)))
                                .thenCombine(CompletableFuture.supplyAsync(()->
                                        boardGroupDao.findOne(boardGroupId).orElse(BoardGroup.empty())), (board, boardGroup)->{
            return allMS.map(moderator->action.apply(boardGroup, board, moderator));
        })).orTimeout(1, TimeUnit.SECONDS).join();
    }
    @Override
    public Optional<Boolean> remove(int volumesId, long memberId)throws IllegalStateException {
        return remove(volumesId, 0, memberId);
    }
    
    @Override
    public Optional<Boolean> remove(int volumesId, long boardId, long memberId)throws IllegalStateException {
        List<BoardModeratorRoleHistory> his = boardModeratorRoleHistoryDao.findAllByMember(memberId).collect(Collectors.toList());
        //20200616移除的角色变化记录
        BoardModeratorRoleHistory removeHis = null;
        //其它的记录
        List<BoardModeratorRoleHistory> otherHis = new ArrayList<>();
        for (BoardModeratorRoleHistory bmr : his) {
            if (bmr.getBoardId() == boardId && bmr.getVolumesId() == volumesId && bmr.getMemberId() == memberId) {
                removeHis = bmr;
            } else {
                otherHis.add(bmr);
            }
        }
        if (null == removeHis) {
            throw new IllegalStateException("角色变化记录不存在");
        }
        //会员更新的角色
        MemberRoleEnum updateRole;
        if (otherHis.isEmpty()) {
            updateRole = MemberRoleEnum.NO;
        } else {
            updateRole = otherHis.stream().map(BoardModeratorRoleHistory::getInvestRole).max(Comparator.comparing(MemberRoleEnum::getSymbol)).get();
        }
        //版主记录
        //版主权限
        try{
            boardModeratorDao.deleteModerator(removeHis);
            moderatorRecallProducer.create(new ModeratorRecallEvent(removeHis, updateRole)).submit();
            return Optional.of(true);
        }catch(Exception e){
            if(logger.isDebugEnabled()){
                logger.debug("版主卸任失败", e);
            }
        }
        return Optional.empty();
    }
    
    @Override
    public Optional<Boolean> edit(long id, BoardModerator updateModerator)throws IllegalArgumentException {
        BoardModerator bm = get(id).orElseThrow(() -> new IllegalArgumentException("版主不存在"));
        bm.setVolumesId(updateModerator.getVolumesId());
        //不能从小版主换成大版主
        //不能从大版主换成小版主
        if (bm.getBoardId() > 0 && updateModerator.getBoardId() > 0) {
            bm.setBoardId(updateModerator.getBoardId());
        }
        bm.setMemberId(updateModerator.getMemberId());
        bm.setMemberNickname(updateModerator.getMemberNickname());
        bm.setStatus(updateModerator.isStatus());
        bm.setLevel(updateModerator.getLevel());
        return boardModeratorDao.edit(bm);
    }
    
    @Override
    public Stream<BoardModeratorReplica> getAll() {
        List<BoardModerator> rs = boardModeratorDao.findAll().collect(Collectors.toList());
        if (null == rs || rs.isEmpty()) {
            return Stream.empty();
        }
        //java.lang.IllegalStateException: Duplicate key 1 (attempted merging values 1 and 0)
        //bug: final Map<Integer,Long> allQueryParam = Commons.collectMap(rs.stream(), BoardModerator::getVolumesId, BoardModerator::getBoardId);
        final Map<Integer,Set<Long>> allQueryParam = rs.stream().collect(Collectors.groupingBy(BoardModerator::getVolumesId, Collectors.mapping(BoardModerator::getBoardId, Collectors.toSet())));
        final TriFunction<BoardGroup, Board, BoardModerator, BoardModeratorReplica> action = (bg, b, bm) ->{
            BoardModeratorReplica bmr = Optional.ofNullable(bm).map(BoardModeratorReplica.copyModerator).orElseGet(BoardModeratorReplica::new);
            if(null != b){
                bmr.setBoard(b);
            }
            if (null !=bg && bg.getId() > 0) {
                bmr.setVolumes(bg);
            }
            return bmr;
        };
        Set<Long> boardIds = allQueryParam.values().stream().flatMap(innerSet->innerSet.stream()).filter(boardId->boardId>0).collect(Collectors.toSet());
        CompletableFuture<Map<Long, Board>> boardMap =  CompletableFuture.supplyAsync(()->boardDao.findAllById(boardIds)).thenApply(boards -> boards.collect(Collectors.toMap(Board::getId, Function.identity()))).completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS);
        CompletableFuture<Map<Integer, BoardGroup>> boardGroupMap = CompletableFuture.supplyAsync(()->boardGroupDao.findAllById(allQueryParam.keySet())).thenApply(boardGroups->boardGroups.collect(Collectors.toMap(BoardGroup::getId, Function.identity()))).completeOnTimeout(Collections.emptyMap(), 1, TimeUnit.SECONDS);
        return boardMap.thenCombine(boardGroupMap, (bmap, bgmap)->{
            return rs.parallelStream().map(ibm->action.apply(bgmap.get(ibm.getVolumesId()), bmap.get(ibm.getBoardId()), ibm));
        }).orTimeout(1, TimeUnit.SECONDS).join();
    }
    
    @Override
    public Stream<BoardModerator> getAllByBoardId(long boardId) {
        if (boardId > 0) { //可能大版主不显示
            return boardModeratorDao.findAllByBoardId(boardId);
        }
        return Stream.empty();
    }
    
    @Override
    public Stream<BoardModerator> getAllUsedByBoardGroupId(int boardGroupId) {
        if(0>=boardGroupId){
            return Stream.empty();
        }
        return boardModeratorDao.findAllUsedByBoardGroup(boardGroupId);
    }
    
    @Override
    public Optional<BoardModerator> get(int boardGroupId, long boardId, long memberId) {
        if (boardGroupId == 0 || boardId == 0 || memberId == 0) {
            return Optional.empty();//.failure("参数不合法或不被接受");
        }
        CompletableFuture<Optional<BoardModerator>> data=CompletableFuture.supplyAsync(()->get(boardId, memberId)).completeOnTimeout(Optional.empty(), 1, TimeUnit.SECONDS);
        return data.thenCombine(
                CompletableFuture.supplyAsync(()->boardModeratorDao.findOneByBoardGroupAndMember(boardGroupId, memberId)).completeOnTimeout(Optional.empty(), 1, TimeUnit.SECONDS),
                (obm, obgbm)->{
                    if(obm.isPresent()){
                        return obm;
                    }
                    return obgbm;
                }).orTimeout(1, TimeUnit.SECONDS).join();
    }
    
    @Override
    public Optional<BoardModerator> get(long id) {
        if (id > 0) {
            return boardModeratorDao.findOne(id);
        }
        return Optional.empty(); //("参数不合法或不被接受");
    }
    private Optional<BoardModerator> storeReplicaModerator(BoardModerator moderator, Member member, BoardModeratorRoleHistory memberRoleHistory){
        try{
            Optional<BoardModerator> data = boardModeratorDao.pushModerator(moderator, memberRoleHistory);
            if(data.isPresent()){
                moderatorBornProducer.create(new ModeratorBornEvent(moderator, member, memberRoleHistory)).submit();
            }
            return data;
        } catch (Exception e) {
            if(logger.isDebugEnabled()){
                logger.debug("版主创建失败", e);
            }
        }
        return Optional.empty();
    }
    private Optional<BoardModerator> get(long boardId, long memberId) {
        if(boardId > 0 && memberId > 0){
            return boardModeratorDao.findOneByBoardAndMember(boardId, memberId);
        }
        return Optional.empty();
    }
}